using System;
using System.IO;

using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.Date;
using Org.BouncyCastle.Utilities.IO;

namespace Org.BouncyCastle.Cms
{
	public class CmsAuthenticatedGenerator
		: CmsEnvelopedGenerator
	{
		/**
		* base constructor
		*/
		public CmsAuthenticatedGenerator()
		{
		}

		/**
		* constructor allowing specific source of randomness
		*
		* @param rand instance of SecureRandom to use
		*/
		public CmsAuthenticatedGenerator(
			SecureRandom rand)
			: base(rand)
		{
		}

		protected AlgorithmIdentifier GetAlgorithmIdentifier(
			string					encryptionOid,
			AlgorithmParameterSpec	paramSpec)
		{
			AlgorithmParameters parameters = CmsEnvelopedHelper.Instance.createAlgorithmParameters(encryptionOID);

			parameters.Init(paramSpec);

			Asn1Encodable asn1Params;
			if (parameters != null)
			{
				Asn1InputStream aIn = new Asn1InputStream(parameters.GetEncoded("ASN.1"));

				asn1Params = aIn.ReadObject();
			}
			else
			{
				asn1Params = DerNull.Instance;
			}

			AlgorithmIdentifier encAlgId = new AlgorithmIdentifier(
				new DerObjectIdentifier(encryptionOid), asn1Params);

			return encAlgId;
		}

		protected AlgorithmParameterSpec generateParameterSpec(
			string		encryptionOid,
			SecretKey	encKey)
		{
			try
			{
				if (encryptionOID.equals(RC2_CBC))
				{
					byte[] iv = new byte[8];

					//
					// mix in a bit extra...
					//
					rand.SetSeed(DateTimeUtilities.CurrentUnixMs);

					rand.NextBytes(iv);

					return new RC2ParameterSpec(encKey.GetEncoded().Length * 8, iv);
				}

				AlgorithmParameterGenerator pGen = CmsEnvelopedHelper.Instance.createAlgorithmParameterGenerator(encryptionOID);

				AlgorithmParameters p = pGen.generateParameters();

				return p.getParameterSpec(typeof(IvParameterSpec));
			}
			catch (GeneralSecurityException e)
			{
				return null;
			}
		}

		// FIXME Use Org.BouncyCastle.Crypto.IO.MacStream
		protected class MacOutputStream
			: BaseOutputStream
		{
			private readonly Stream outStr;
			private readonly IMac mac;

			MacOutputStream(Stream outStr, IMac mac)
			{
				this.outStr = outStr;
				this.mac = mac;
			}

			public override void WriteByte(
				byte b)
			{
				mac.Update(b);
				outStr.WriteByte(b);
			}

			public override void Write(
				byte[]	buf,
				int		off,
				int		len)
			{
				mac.BlockUpdate(buf, off, len);
				outStr.Write(buf, off, len);
			}

			public override void Close()
			{
				outStr.Close();
			}

			public byte[] GetMac()
			{
				return mac.DoFinal();
			}
		}
	}
}
